home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Development Tools & Languages / Dylan Related / Mindy / Mindy 1.2 - portable sources / libraries / string-ext / interface.dylan < prev    next >
Encoding:
Text File  |  1995-03-15  |  15.0 KB  |  443 lines  |  [TEXT/ttxt]

  1. module:   regular-expressions
  2. author:   Nick Kramer (nkramer@cs.cmu.edu)
  3. synopsis: This provides a useable interface for users. Functions 
  4.       defined outside this file are really too strange and quirky 
  5.           to be of use to people.
  6. copyright:  Copyright (C) 1994, Carnegie Mellon University.
  7.             All rights reserved.
  8. rcs-header: $Header: interface.dylan,v 1.1 94/11/08 22:56:51 nkramer Exp $
  9.  
  10. //======================================================================
  11. //
  12. // Copyright (c) 1994  Carnegie Mellon University
  13. // All rights reserved.
  14. // 
  15. // Use and copying of this software and preparation of derivative
  16. // works based on this software are permitted, including commercial
  17. // use, provided that the following conditions are observed:
  18. // 
  19. // 1. This copyright notice must be retained in full on any copies
  20. //    and on appropriate parts of any derivative works.
  21. // 2. Documentation (paper or online) accompanying any system that
  22. //    incorporates this software, or any part of it, must acknowledge
  23. //    the contribution of the Gwydion Project at Carnegie Mellon
  24. //    University.
  25. // 
  26. // This software is made available "as is".  Neither the authors nor
  27. // Carnegie Mellon University make any warranty about the software,
  28. // its performance, or its conformity to any specification.
  29. // 
  30. // Bug reports, questions, comments, and suggestions should be sent by
  31. // E-mail to the Internet address "gwydion-bugs@cs.cmu.edu".
  32. //
  33. //======================================================================
  34.  
  35. // Functions that aren't exported are marked as such.  Everything else
  36. // is exported.
  37.  
  38.  
  39. // Find the position of a regular expression inside a string.  If the
  40. // regexp is not found, return #f, otherwise return a variable number
  41. // of marks.
  42. //
  43. define open generic regexp-position
  44.     (big :: <string>, regexp :: <string>, #key start:,
  45.      end:, case-sensitive:)
  46.  => (regexp-start :: <integer?>, #rest marks :: <integer?>);
  47.  
  48. define method regexp-position
  49.     (big :: <string>, regexp :: <string>, #key start: big-start = 0,
  50.      end: big-end = #f, case-sensitive = #f)
  51.  => (regexp-start :: <integer?>, #rest marks :: <integer?>);
  52.   let substring = subsequence(big, start: big-start,
  53.                   end: if (big-end) big-end else size(big) end);
  54.   let comparison = if (case-sensitive) \= else case-insensitive-equal end;
  55.   let char-set-class = if (case-sensitive) 
  56.              <case-sensitive-character-set>;
  57.                else
  58.              <case-insensitive-character-set>;
  59.                end if;
  60.   let (parsed-regexp, last-group) = parse(regexp, char-set-class);
  61.   let (matched, marks)
  62.     = if (is-anchored?(parsed-regexp))
  63.     anchored-match-root?(parsed-regexp, substring, comparison,
  64.                      last-group + 1);
  65.       else
  66.     match-root?(parsed-regexp, substring, comparison, last-group + 1);
  67.       end if;
  68.  
  69.   if (matched)  
  70.     apply(values, adjust-marks!(marks, big-start));
  71.   else
  72.     #f  
  73.   end if;
  74. end method regexp-position;
  75.   
  76.  
  77. // Returns an appropriate matcher function that acts just like
  78. // regexp-position curried.  If the user can deal with a positioner
  79. // that works only on byte strings and doesn't return any marks, and
  80. // doesn't mind the extra wait while the thing compiles, he can get a
  81. // positioner that executes considerably faster.
  82. //
  83. define open generic make-regexp-positioner
  84.     (regexp :: <string>, #key byte-characters-only:,
  85.      need-marks:, maximum-compile:, case-sensitive:)
  86.  => regexp-positioner :: <function>;
  87.  
  88. define method make-regexp-positioner (regexp :: <string>, 
  89.                       #key byte-characters-only = #f,
  90.                       need-marks = #t,
  91.                       maximum-compile = #f,
  92.                       case-sensitive = #f)
  93.  => regexp-positioner :: <function>;
  94.   let comparison = if (case-sensitive) \= else case-insensitive-equal end;
  95.   let char-set-class = if (case-sensitive) 
  96.              <case-sensitive-character-set>;
  97.                else
  98.              <case-insensitive-character-set>;
  99.                end if;
  100.   let (parsed-regexp, last-group, has-backrefs, alternatives, quantifiers) 
  101.     = parse(regexp, char-set-class);
  102.   let match-root-function = if (is-anchored?(parsed-regexp))
  103.                   anchored-match-root?
  104.                 else
  105.                   match-root?;
  106.                  end if;
  107.   if (~maximum-compile | has-backrefs | ~byte-characters-only | need-marks)
  108.     method (big :: <string>, #key start: big-start = 0,
  109.         end: big-end = #f)
  110.      => (regexp-start :: <integer?>, #rest marks :: <integer?>);
  111.       let substring
  112.     = subsequence(big, start: big-start,
  113.               end: if (big-end) big-end else size(big) end);
  114.       let (matched, marks) = match-root-function(parsed-regexp, substring,
  115.                          comparison, last-group + 1);
  116.       if (matched)  
  117.     apply(values, adjust-marks!(marks, big-start));
  118.       else
  119.     #f  
  120.       end if;
  121.     end method;
  122.   else
  123.     let (nfa-begin, nfa-end) = build-nfa(parsed-regexp);
  124.        // Now modify the DFA to accomodate a substring match rather
  125.        // than matching the entire string
  126.     let dot = make(<set-atom>, set: any-char);
  127.     let star = make(<e-state>, next-state: dot, other-next-state: nfa-begin);
  128.     dot.next-state := star;
  129.     let dfa = nfa-to-dfa(star, nfa-end, comparison);
  130.     method (big :: <string>, #key start: big-start = 0,
  131.         end: big-end = #f)
  132.         => answer :: <boolean>;
  133.       let substring
  134.     = subsequence(big, start: big-start,
  135.               end: if (big-end) big-end else size(big) end);
  136.       sim-dfa(dfa, substring);
  137.     end method;
  138.   end if;
  139. end method make-regexp-positioner;
  140.  
  141.  
  142. // A method used by regexp-position and make-regexp-positioner.  Just
  143. // adds offset to every element of the sequence that isn't #f.  There
  144. // isn't any actual mutation, but just in case someone wants to
  145. // rewrite it to involve mutation...
  146. //
  147. define method adjust-marks! (marks :: <sequence>, offset :: <integer>)
  148.  => adjusted-marks :: <sequence>;
  149.   map(method (a-mark :: <integer?>)
  150.     if (a-mark = #f)  #f  else  (a-mark + offset)  end;
  151.       end method, 
  152.       marks);
  153. end method adjust-marks!;
  154.  
  155.  
  156. define open generic regexp-replace
  157.     (input :: <string>, regexp :: <string>, new-substring :: <string>,
  158.      #key count:, case-sensitive:, start:, end:)
  159.  => changed-string :: <string>;
  160.  
  161. define method regexp-replace (input :: <string>, 
  162.                   regexp :: <string>, new-substring :: <string>,
  163.                   #key count = #f,
  164.                   case-sensitive = #f,
  165.                   start = 0, end: input-end = #f)
  166.     => changed-string :: <string>;
  167.   let positioner
  168.     = make-regexp-positioner(regexp, case-sensitive: case-sensitive);
  169.   do-replacement(positioner, new-substring, input, start, 
  170.          input-end, count, #t);
  171. end method regexp-replace;
  172.  
  173.  
  174. define open generic make-regexp-replacer
  175.     (regexp :: <string>, #key replace-with:,
  176.      case-sensitive:)
  177.  => replacer :: <function>;
  178.  
  179. define method make-regexp-replacer (regexp :: <string>, 
  180.                     #key replace-with,
  181.                     case-sensitive = #f)
  182.     => replacer :: <function>;
  183.   let positioner
  184.     = make-regexp-positioner(regexp, case-sensitive: case-sensitive);
  185.   if (replace-with)
  186.     method (input :: <string>, #key count: count, 
  187.         start = 0, end: input-end = #f)
  188.      => string :: <string>;
  189.       do-replacement(positioner, replace-with, input, start, 
  190.              input-end, count, #t);
  191.     end method;
  192.   else
  193.     method (input :: <string>, new-substring :: <string>, 
  194.         #key count = #f, case-sensitive = #f,
  195.         start = 0, end: input-end = #f)
  196.      => string :: <string>;
  197.       do-replacement(positioner, new-substring, input, 
  198.              start, input-end, count, #t);
  199.     end method;
  200.   end if;
  201. end method make-regexp-replacer;
  202.  
  203.  
  204. // equivalent of Perl's tr.  Does a character by character translation.
  205. //
  206. define open generic translate (input :: <string>, from-set :: <string>,
  207.               to-set :: <string>, #key delete:, start:, end:)
  208.  => output :: <string>;
  209.  
  210.  
  211. //The existing methods only work on byte-strings.
  212. //
  213. define method translate (input :: <byte-string>,
  214.              from-set :: <byte-string>,
  215.              to-set :: <byte-string>,
  216.              #key delete: delete = #f,
  217.              start = 0, end: input-end = #f)
  218.     => output :: <byte-string>;
  219.   let table = make-translation-table(from-set, to-set, delete: delete);
  220.   run-translator(input, table, start, input-end | size(input));
  221. end method translate;
  222.  
  223.  
  224. define open generic make-translator
  225.     (from-set :: <string>, to-set :: <string>, #key delete:)
  226.  => translator :: <function>;
  227.  
  228. // Again, only byte-strings handled here
  229. //
  230. define method make-translator (from-set :: <byte-string>,
  231.                    to-set :: <byte-string>,
  232.                    #key delete: delete = #f)
  233.     => translator :: <function>;
  234.   let table = make-translation-table(from-set, to-set, delete: delete);
  235.   method (input :: <byte-string>, #key start = 0, end: input-end = #f)
  236.    => output :: <byte-string>;
  237.     run-translator(input, table, start, input-end | size(input));
  238.   end method;
  239. end method make-translator;
  240.  
  241.  
  242. // Used by translate.  Not exported.
  243. //
  244. define method make-translation-table (from-set :: <byte-string>, 
  245.                       to-set :: <byte-string>,
  246.                       #key delete: delete = #f)
  247.     => table :: <byte-character-table>;
  248.  
  249.   let from-index = 0;
  250.   let to-index = 0;
  251.   let previous-from = #f;
  252.   let previous-to = #f;
  253.  
  254.      // These local methods are identical except for the 
  255.      // choice of variable names and next-from-character signals end of
  256.      // string rather than repeating the last character indefinitely like
  257.      // next-to-character does.
  258.   local method next-from-character ()
  259.       if (from-index >= size(from-set))
  260.         #f;
  261.       elseif (from-set[from-index] = '\\')
  262.         from-index := from-index + 2;
  263.         previous-from := from-set[from-index - 1];
  264.       elseif (from-set[from-index] = '-')
  265.         if (previous-from = from-set[from-index + 1])
  266.           from-index := from-index + 1;
  267.           from-set[from-index];
  268.         else
  269.           previous-from := successor(previous-from); 
  270.                   // and return that value
  271.         end if;
  272.       else
  273.         from-index := from-index + 1;
  274.         previous-from := from-set[from-index - 1];
  275.       end if;
  276.     end method next-from-character;
  277.  
  278.   local method next-to-character ()
  279.       if (to-index >= size(to-set))
  280.         if (delete)  #f  else  last(to-set)  end;
  281.       elseif (to-set[to-index] = '\\')
  282.         to-index := to-index + 2;
  283.         previous-to := to-set[to-index - 1];
  284.       elseif (to-set[to-index] = '-')
  285.         if (previous-to = to-set[to-index + 1])
  286.           to-index := to-index + 1;
  287.           to-set[to-index];
  288.         else
  289.           previous-to := successor(previous-to); 
  290.                   // and return that value
  291.         end if;
  292.       else
  293.         to-index := to-index + 1;
  294.         previous-to := to-set[to-index - 1];
  295.       end if;
  296.     end method next-to-character;
  297.  
  298.   let table = make(<byte-character-table>);
  299.   for (dummy keyed-by c in table)                  // Initialize table
  300.     table[c] := c;
  301.   end for;
  302.  
  303.   for (from-char = next-from-character() then next-from-character(),
  304.        to-char = next-to-character() then next-to-character(),
  305.        until from-char = #f)
  306.     table[from-char] := to-char;
  307.   end for;
  308.  
  309.   table;
  310. end method make-translation-table;
  311.  
  312.  
  313. // Used by translate.  Not exported.
  314. //
  315. define method run-translator (source :: <byte-string>, 
  316.                   table :: <byte-character-table>, 
  317.                   start-index :: <integer>, end-index :: <integer>)
  318.     => output :: <byte-string>;
  319.   let dest-string = copy-sequence(source);
  320.   let dest-index = start-index;
  321.   for (source-index from start-index below end-index)
  322.     let char = source[source-index];
  323.     if (table[char] ~= #f)
  324.       dest-string[dest-index] := table[char];
  325.       dest-index := dest-index + 1;
  326.     end if;
  327.   end for;
  328.  
  329.       // Now resize dest-string, because deleting characters in the
  330.       // translation would make dest-string shorter than we've
  331.       // allocated.
  332.   if (dest-index = end-index)
  333.     dest-string;
  334.   else
  335.     replace-subsequence!(dest-string, "", start: dest-index, end: end-index);
  336.   end if;
  337. end method run-translator;
  338.  
  339.  
  340. // Like Perl's split function
  341. //
  342. define open generic split (pattern :: <string>, input :: <string>, 
  343.              #key count:, remove-empty-items:,
  344.              start:, end:);
  345. // => #rest whole-bunch-of-strings :: <string>;
  346.  
  347. define method split (pattern :: <string>, input :: <string>, 
  348.              #key count = #f, remove-empty-items = #t,
  349.              start = 0, end: input-end = #f)
  350.  => #rest whole-bunch-of-strings :: <string>;
  351.   let positioner = make-regexp-positioner(pattern);
  352.   split-string(positioner, input, start, input-end | size(input),
  353.            count, remove-empty-items);
  354. end method split;
  355.  
  356.  
  357. define open generic make-splitter (pattern :: <string>) => splitter :: <function>;
  358.  
  359. define method make-splitter (pattern :: <string>) => splitter :: <function>;
  360.   let positioner = make-regexp-positioner(pattern);
  361.   method (string :: <string>, #key count = #f,
  362.       remove-empty-items = #t, start = 0, end: input-end = #f)
  363.    => #rest whole-bunch-of-strings :: <string>;
  364.     split-string(positioner, string, start, input-end | size(string), 
  365.          count, remove-empty-items);
  366.   end method;
  367. end method make-splitter;
  368.   
  369.  
  370. // Used by split.  Not exported.
  371. //
  372. define method split-string (positioner :: <function>, input :: <string>, 
  373.                 start :: <integer>, input-end :: <integer>,
  374.                 count :: <integer?>, 
  375.                 remove-empty-items :: <object>)
  376.  => #rest whole-bunch-of-strings :: <string>;
  377.   let strings = make(<deque>);
  378.   block (done)
  379.     let end-of-last-match = 0;
  380.     let start-of-where-to-look = start;
  381.     let string-number = 1;    // Since count: starts at 1, so 
  382.                               // should string-number
  383.     while (#t)
  384.       let (substring-start, substring-end)
  385.     = positioner(input, start: start-of-where-to-look, end: input-end);
  386.       if (~substring-start | (count & (count <= string-number)))
  387.     strings := push-last(strings, 
  388.                  copy-sequence(input, start: end-of-last-match));
  389.     done(); 
  390.       elseif ((substring-start = start-of-where-to-look)
  391.         &  remove-empty-items)
  392.           // delimited item is empty
  393.     end-of-last-match := substring-end;
  394.     start-of-where-to-look := end-of-last-match;
  395.       else
  396.     strings := push-last(strings,
  397.                  copy-sequence(input, start: end-of-last-match, 
  398.                        end: substring-start));
  399.     string-number := string-number + 1;
  400.     end-of-last-match := substring-end;
  401.     start-of-where-to-look := end-of-last-match;
  402.       end if;
  403.     end while;
  404.   end block;
  405.   apply(values, strings);
  406. end method split-string;
  407.  
  408.  
  409. // join--like Perl's join
  410. //
  411. define open generic join (delimiter :: <string>, #rest strings)
  412.  => big-string :: <string>;
  413.  
  414. // This is not really any more efficient than concatenate-as, but it's
  415. // more convenient.
  416. //
  417. define method join (delimiter :: <string>, #rest strings)
  418.  => big-string :: <string>;
  419.   let length = max(0, (size(strings) - 1 ) * size(delimiter));
  420.   for (string in strings)
  421.     length := length + size(string);
  422.   end for;
  423.   let big-string = make(<unicode-string>, size: length);
  424.   let big-index = 0;
  425.   for (i from 0 to size(strings) - 2)  // Don't iterate over the last string
  426.     let string = strings[i];
  427.     let new-index = big-index + size(string);
  428.     big-string := replace-subsequence!(big-string, string, 
  429.                        start: big-index, end: new-index);
  430.     big-index := new-index;
  431.     let new-index = big-index + size(delimiter);
  432.     big-string := replace-subsequence!(big-string, delimiter, 
  433.                        start: big-index, end: new-index);
  434.     big-index := new-index;
  435.   end for;
  436.   if (size(strings) > 0)
  437.     big-string 
  438.       := replace-subsequence!(big-string, last(strings), 
  439.                   start: big-index, end: size(big-string));
  440.   end if;
  441.   big-string;
  442. end method join;
  443.